package com.coai.file.service

import com.coai.common.exception.ValidationException
import com.coai.file.dto.*
import com.coai.file.model.*
import com.coai.file.repository.ChunkUploadRecordRepository
import com.coai.file.repository.FileMetadataRepository
import io.minio.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.apache.commons.io.FilenameUtils
import org.apache.tika.Tika
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.multipart.MultipartFile
import java.io.InputStream
import java.security.MessageDigest
import java.time.LocalDateTime
import java.util.*

@Service
class FileService(
    private val minioClient: MinioClient,
    private val fileMetadataRepository: FileMetadataRepository,
    private val chunkUploadRepository: ChunkUploadRecordRepository,
    private val fileEventService: FileEventService
) {
    
    @Value("\${minio.bucket-name}")
    private lateinit var bucketName: String
    
    @Value("\${minio.base-url}")
    private lateinit var baseUrl: String
    
    @Value("\${file.max-size:104857600}") // 默认100MB
    private var maxFileSize: Long = 104857600
    
    @Value("\${file.user-quota:10737418240}") // 默认10GB
    private var userQuota: Long = 10737418240
    
    private val tika = Tika()
    
    /**
     * 上传文件
     */
    @Transactional
    suspend fun uploadFile(
        file: MultipartFile,
        uploaderId: String,
        conversationId: String? = null,
        messageId: String? = null
    ): FileUploadResponse = withContext(Dispatchers.IO) {
        
        // 验证文件大小
        if (file.size > maxFileSize) {
            throw ValidationException("文件大小超过限制（最大${maxFileSize / 1024 / 1024}MB）")
        }
        
        // 检查用户存储配额
        val currentUsage = fileMetadataRepository.calculateUserStorageUsage(uploaderId)
        if (currentUsage + file.size > userQuota) {
            throw ValidationException("存储空间不足")
        }
        
        // 计算MD5
        val md5Hash = calculateMD5(file.inputStream)
        
        // 检查是否已存在相同文件（秒传）
        val existingFile = fileMetadataRepository.findByMd5Hash(md5Hash)
        if (existingFile != null && existingFile.status == FileStatus.READY) {
            // 创建新的文件记录，但共享存储
            val newFile = existingFile.copy(
                id = null,
                uploaderId = uploaderId,
                conversationId = conversationId,
                messageId = messageId,
                originalName = file.originalFilename ?: "unknown",
                downloadCount = 0,
                createdAt = LocalDateTime.now()
            )
            val saved = fileMetadataRepository.save(newFile)
            return@withContext toUploadResponse(saved)
        }
        
        // 生成存储文件名
        val storedName = UUID.randomUUID().toString()
        val extension = FilenameUtils.getExtension(file.originalFilename ?: "")
        val storedNameWithExt = if (extension.isNotEmpty()) "$storedName.$extension" else storedName
        
        // 检测MIME类型
        val mimeType = tika.detect(file.inputStream)
        val category = detectCategory(mimeType)
        
        // 上传到MinIO
        ensureBucketExists()
        val objectName = buildObjectName(uploaderId, storedNameWithExt)
        
        minioClient.putObject(
            PutObjectArgs.builder()
                .bucket(bucketName)
                .`object`(objectName)
                .stream(file.inputStream, file.size, -1)
                .contentType(mimeType)
                .build()
        )
        
        // 生成访问URL
        val url = "$baseUrl/$bucketName/$objectName"
        
        // 保存文件元数据
        val metadata = FileMetadata(
            originalName = file.originalFilename ?: "unknown",
            storedName = storedNameWithExt,
            mimeType = mimeType,
            size = file.size,
            extension = extension,
            storagePath = objectName,
            url = url,
            uploaderId = uploaderId,
            conversationId = conversationId,
            messageId = messageId,
            category = category,
            md5Hash = md5Hash,
            status = FileStatus.UPLOADED
        )
        
        val saved = fileMetadataRepository.save(metadata)
        
        // 异步处理（生成缩略图等）
        processFileAsync(saved)
        
        // 发送事件
        fileEventService.publishFileUploaded(saved)
        
        toUploadResponse(saved)
    }
    
    /**
     * 初始化分片上传
     */
    @Transactional
    suspend fun initChunkUpload(
        request: InitChunkUploadRequest,
        uploaderId: String
    ): InitChunkUploadResponse = withContext(Dispatchers.IO) {
        
        // 检查MD5，支持秒传
        request.md5Hash?.let { hash ->
            val existingFile = fileMetadataRepository.findByMd5Hash(hash)
            if (existingFile != null && existingFile.status == FileStatus.READY) {
                return@withContext InitChunkUploadResponse(
                    uploadId = "",
                    chunkSize = request.chunkSize,
                    totalChunks = 0,
                    existingFile = toUploadResponse(existingFile)
                )
            }
        }
        
        val uploadId = UUID.randomUUID().toString()
        val totalChunks = ((request.fileSize + request.chunkSize - 1) / request.chunkSize).toInt()
        
        val record = ChunkUploadRecord(
            uploadId = uploadId,
            fileName = request.fileName,
            totalSize = request.fileSize,
            totalChunks = totalChunks,
            uploaderId = uploaderId
        )
        
        chunkUploadRepository.save(record)
        
        InitChunkUploadResponse(
            uploadId = uploadId,
            chunkSize = request.chunkSize,
            totalChunks = totalChunks
        )
    }
    
    /**
     * 上传文件分片
     */
    @Transactional
    suspend fun uploadChunk(
        uploadId: String,
        chunkIndex: Int,
        chunkData: InputStream,
        chunkSize: Long
    ): ChunkUploadStatusResponse = withContext(Dispatchers.IO) {
        
        val record = chunkUploadRepository.findByUploadId(uploadId)
            ?: throw ValidationException("上传记录不存在")
        
        if (record.status != ChunkUploadStatus.IN_PROGRESS) {
            throw ValidationException("上传已结束")
        }
        
        // 保存分片到临时存储
        val chunkPath = "chunks/$uploadId/chunk-$chunkIndex"
        minioClient.putObject(
            PutObjectArgs.builder()
                .bucket(bucketName)
                .`object`(chunkPath)
                .stream(chunkData, chunkSize, -1)
                .build()
        )
        
        // 更新已上传分片列表
        val uploadedChunks = if (record.uploadedChunks.isEmpty()) {
            mutableListOf()
        } else {
            record.uploadedChunks.split(",").map { it.toInt() }.toMutableList()
        }
        
        if (!uploadedChunks.contains(chunkIndex)) {
            uploadedChunks.add(chunkIndex)
            uploadedChunks.sort()
            record.uploadedChunks = uploadedChunks.joinToString(",")
            chunkUploadRepository.save(record)
        }
        
        ChunkUploadStatusResponse(
            uploadId = uploadId,
            fileName = record.fileName,
            totalChunks = record.totalChunks,
            uploadedChunks = uploadedChunks,
            progress = uploadedChunks.size.toDouble() / record.totalChunks,
            status = record.status.name
        )
    }
    
    /**
     * 完成分片上传
     */
    @Transactional
    suspend fun completeChunkUpload(
        uploadId: String,
        conversationId: String? = null,
        messageId: String? = null
    ): FileUploadResponse = withContext(Dispatchers.IO) {
        
        val record = chunkUploadRepository.findByUploadId(uploadId)
            ?: throw ValidationException("上传记录不存在")
        
        val uploadedChunks = record.uploadedChunks.split(",").map { it.toInt() }
        if (uploadedChunks.size != record.totalChunks) {
            throw ValidationException("分片未全部上传")
        }
        
        // 合并分片
        val storedName = UUID.randomUUID().toString()
        val extension = FilenameUtils.getExtension(record.fileName)
        val storedNameWithExt = if (extension.isNotEmpty()) "$storedName.$extension" else storedName
        val objectName = buildObjectName(record.uploaderId, storedNameWithExt)
        
        // TODO: 合并分片的具体实现
        // 这里简化处理，实际需要从MinIO读取所有分片并合并
        
        val mimeType = tika.detect(record.fileName)
        val category = detectCategory(mimeType)
        val url = "$baseUrl/$bucketName/$objectName"
        
        val metadata = FileMetadata(
            originalName = record.fileName,
            storedName = storedNameWithExt,
            mimeType = mimeType,
            size = record.totalSize,
            extension = extension,
            storagePath = objectName,
            url = url,
            uploaderId = record.uploaderId,
            conversationId = conversationId,
            messageId = messageId,
            category = category,
            status = FileStatus.UPLOADED
        )
        
        val saved = fileMetadataRepository.save(metadata)
        
        // 更新上传记录
        record.status = ChunkUploadStatus.COMPLETED
        record.fileId = saved.id
        chunkUploadRepository.save(record)
        
        // 清理临时分片
        cleanupChunks(uploadId)
        
        // 异步处理
        processFileAsync(saved)
        
        fileEventService.publishFileUploaded(saved)
        
        toUploadResponse(saved)
    }
    
    /**
     * 下载文件
     */
    suspend fun downloadFile(fileId: String, userId: String): Pair<InputStream, FileMetadata> = 
        withContext(Dispatchers.IO) {
        
        val file = fileMetadataRepository.findById(fileId)
            .orElseThrow { ValidationException("文件不存在") }
        
        if (file.status == FileStatus.DELETED) {
            throw ValidationException("文件已删除")
        }
        
        // 增加下载次数
        file.downloadCount++
        fileMetadataRepository.save(file)
        
        val stream = minioClient.getObject(
            GetObjectArgs.builder()
                .bucket(bucketName)
                .`object`(file.storagePath)
                .build()
        )
        
        fileEventService.publishFileDownloaded(file, userId)
        
        Pair(stream, file)
    }
    
    /**
     * 删除文件
     */
    @Transactional
    suspend fun deleteFile(fileId: String, userId: String): Unit = withContext(Dispatchers.IO) {
        
        val file = fileMetadataRepository.findById(fileId)
            .orElseThrow { ValidationException("文件不存在") }
        
        // 只有上传者可以删除
        if (file.uploaderId != userId) {
            throw ValidationException("无权删除此文件")
        }
        
        // 软删除
        file.status = FileStatus.DELETED
        fileMetadataRepository.save(file)
        
        // TODO: 异步物理删除（检查是否有其他用户引用）
        
        fileEventService.publishFileDeleted(file)
    }
    
    /**
     * 获取文件列表
     */
    fun getUserFiles(
        userId: String,
        category: FileCategory? = null,
        page: Int,
        size: Int
    ): FileListResponse {
        val pageable = PageRequest.of(page, size)
        
        val files = if (category != null) {
            fileMetadataRepository.findByUploaderIdAndCategoryOrderByCreatedAtDesc(
                userId, category, pageable
            )
        } else {
            fileMetadataRepository.findByUploaderIdOrderByCreatedAtDesc(userId, pageable)
        }
        
        return FileListResponse(
            files = files.map { toInfoDto(it) },
            hasMore = files.size >= size,
            total = files.size.toLong() // TODO: 实际应查询总数
        )
    }
    
    /**
     * 获取存储使用统计
     */
    fun getStorageUsage(userId: String): StorageUsageDto {
        val files = fileMetadataRepository.findByUploaderIdOrderByCreatedAtDesc(
            userId, PageRequest.of(0, 10000)
        ).filter { it.status != FileStatus.DELETED }
        
        val totalSize = files.sumOf { it.size }
        val byCategory = files.groupBy { it.category }
            .mapValues { (_, files) ->
                CategoryUsage(
                    count = files.size.toLong(),
                    size = files.sumOf { it.size }
                )
            }
        
        return StorageUsageDto(
            totalSize = totalSize,
            fileCount = files.size.toLong(),
            byCategory = byCategory,
            quota = userQuota,
            usagePercentage = (totalSize.toDouble() / userQuota) * 100
        )
    }
    
    // 辅助方法
    
    private fun ensureBucketExists() {
        val exists = minioClient.bucketExists(
            BucketExistsArgs.builder().bucket(bucketName).build()
        )
        if (!exists) {
            minioClient.makeBucket(
                MakeBucketArgs.builder().bucket(bucketName).build()
            )
        }
    }
    
    private fun buildObjectName(userId: String, fileName: String): String {
        val date = LocalDateTime.now()
        return "files/$userId/${date.year}/${date.monthValue}/$fileName"
    }
    
    private fun detectCategory(mimeType: String): FileCategory {
        return when {
            mimeType.startsWith("image/") -> FileCategory.IMAGE
            mimeType.startsWith("video/") -> FileCategory.VIDEO
            mimeType.startsWith("audio/") -> FileCategory.AUDIO
            mimeType.startsWith("application/pdf") -> FileCategory.DOCUMENT
            mimeType.startsWith("application/msword") -> FileCategory.DOCUMENT
            mimeType.startsWith("application/vnd.") -> FileCategory.DOCUMENT
            mimeType.contains("zip") || mimeType.contains("rar") -> FileCategory.ARCHIVE
            else -> FileCategory.OTHER
        }
    }
    
    private fun calculateMD5(inputStream: InputStream): String {
        val md = MessageDigest.getInstance("MD5")
        val buffer = ByteArray(8192)
        var read: Int
        
        while (inputStream.read(buffer).also { read = it } > 0) {
            md.update(buffer, 0, read)
        }
        
        return md.digest().joinToString("") { "%02x".format(it) }
    }
    
    private fun processFileAsync(file: FileMetadata) {
        // TODO: 异步处理任务
        // 1. 生成缩略图（图片/视频）
        // 2. 提取元数据（视频时长、图片尺寸等）
        // 3. 病毒扫描
        // 4. AI内容识别
    }
    
    private fun cleanupChunks(uploadId: String) {
        // TODO: 清理临时分片文件
    }
    
    private fun toUploadResponse(file: FileMetadata): FileUploadResponse {
        return FileUploadResponse(
            id = file.id!!,
            originalName = file.originalName,
            url = file.url,
            thumbnailUrl = file.thumbnailUrl,
            mimeType = file.mimeType,
            size = file.size,
            category = file.category,
            md5Hash = file.md5Hash,
            createdAt = file.createdAt
        )
    }
    
    private fun toInfoDto(file: FileMetadata): FileInfoDto {
        return FileInfoDto(
            id = file.id!!,
            originalName = file.originalName,
            storedName = file.storedName,
            mimeType = file.mimeType,
            size = file.size,
            extension = file.extension,
            url = file.url,
            thumbnailUrl = file.thumbnailUrl,
            uploaderId = file.uploaderId,
            uploaderName = null, // TODO
            conversationId = file.conversationId,
            messageId = file.messageId,
            status = file.status,
            category = file.category,
            downloadCount = file.downloadCount,
            expiresAt = file.expiresAt,
            metadata = null, // TODO: 解析JSON
            createdAt = file.createdAt
        )
    }
}
